/**********************************************************
 * Author    : Riwut Libinuko (cakriwut@gmail.com)
 * Blog      : http://blog.libinuko.com
 * Copyright : Riwut Libinuko (c) 2009 - 2010
 * 
 **********************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint;
using System.Data;
using System.Linq;
using System.IO;
using IdeasFree.SharePoint;
using IdeasFree.SharePoint.ScheduleReminder.Helper;

namespace IdeasFree.SharePoint.ScheduleReminder.SPJobs
{
    /// <summary>
    /// ScheduleReminder Job Definition
    /// </summary>
    public class ScheduleReminderJobDefinition : SPJobDefinition
    {
        const string NOTIFICATIONHISTORY = "Notification History";
        public ScheduleReminderJobDefinition()
            : base()
        {
            this.Title = Consts.JobTitle;
        }

        public ScheduleReminderJobDefinition(string name, SPService service, SPServer server, SPJobLockType locktype)
            :
            base(name, service, server, locktype)
        {
            this.Title = Consts.JobTitle;
        }

        public ScheduleReminderJobDefinition(string name, SPWebApplication webApplication, SPServer server, SPJobLockType locktype)
            : base(name, webApplication, server, locktype)
        {
            this.Title = Consts.JobTitle;
        }

        /// <summary>
        /// 1. Query _x_Notify_List
        /// 2. For each items, query event calendar
        /// 3. Move _x_notify_ listitem to list history
        /// 4. If more events, add new listitem to _x_notify for next event
        /// </summary>
        /// <param name="targetInstanceId"></param>
        public override void Execute(Guid targetInstanceId)
        {
#if DEBUG
            Log.Writer("Schedule Monitor running..");
#endif
            var rootWebs = this.WebApplication.Sites.OfType<SPSite>().Select(s => s.RootWeb);
            foreach(SPWeb spWeb in rootWebs)
            {                                

                    // Query to all _x_notify_ list in site collection
                    DataTable dt = GetSiteDate(spWeb);// spWeb.GetSiteData(spdQuery);

                    if (dt.Rows.Count <= 0)
                        continue;

                    SPWeb currentSPWeb=null;
                    SPList calendarSPList = null;
                    string curWebGuid = String.Empty;
                    string curCalendarListGuid = String.Empty;

                    // For each item, open the web and calendar list
                    // Query for the item id in calendar list
                    // Results table rows,
                    // WebId => website GUID , indicates location of the result list
                    // ListId => list GUID, indicates name of result list from website GUID
                    // Id => list item ID, indicates ID of the resulst list item
                    foreach (DataRow row in dt.Select("","WebId DESC"))
                    {                        
                        if (!(row[Consts.WebIDField] == null || 
                            row[Consts.TitleField] == null || 
                            row[Consts.ListItemIDField] == null))
                        {
                            string strWebGuid = row[Consts.WebIDField].ToString();   // Website GUID 
                            string strListGuid = row[Consts.TitleField].ToString();  // Calendar List GUID
                            if (curWebGuid != strWebGuid)  // Check if we need to call new web
                            {
                                curWebGuid = strWebGuid;
                                    Guid webGuid = new Guid(curWebGuid);
                                    currentSPWeb = spWeb.Site.OpenWeb(webGuid);
                            }
                            if (currentSPWeb == null)
                                continue;

                            SPList notifySPList = currentSPWeb.Lists[new Guid(row[Consts.ListIDField].ToString())];
                            int notifyListItemID;
                            int.TryParse(row["Id"].ToString(), out notifyListItemID);
                        
                             if(row[Consts.ListItemIDField] != null)
                             {
                                 int listItemID;
                                 SPTimeZone tZone = spWeb.RegionalSettings.TimeZone;
                                 // ETS reference
                                 DateTime etsDate = tZone.UTCToLocalTime(SPUtility.CreateDateTimeFromISO8601DateTimeString(row[Consts.EstTimetoSendField].ToString()));

                                 if (int.TryParse(row[Consts.ListItemIDField].ToString(), out listItemID))
                                 {
                                     // Query calendar
                                     if (strListGuid != curCalendarListGuid)  // Check if we need to call new calendar
                                     {
                                         try
                                         {
                                             curCalendarListGuid = strListGuid;
                                             Guid listGuid = new Guid(curCalendarListGuid);
                                             calendarSPList = currentSPWeb.Lists[listGuid];
                                         }
                                         catch (Exception Exp)
                                         {
                                             Log.Writer(String.Format("Error : Processing item, webId:{0}, listId:{1}, itemId:{2}\nException: {3}",
                                                 row[Consts.WebIDField], row[Consts.ListIDField], row["Id"], Exp.Message + " " + Exp.StackTrace));
                                             // Delete notify item, because it's invalid
                                             goto DeleteNotifyItem;
                                         }
                                     }
                                     if (calendarSPList == null)
                                         continue;                    // Orphan list, continue                                     

                                     SPListItemCollection calendarItems = GetRecurrence(calendarSPList, row[Consts.ListItemIDField].ToString());

                                     if (calendarItems.Count <= 0)
                                     {
                                         goto DeleteNotifyItem;
                                     }

                                     foreach (SPListItem item in calendarItems)
                                     {
                                         string[] reminderTimeCollection = item[Consts.ReminderField].ToString().Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries);
                                         DateTime eventDate = (DateTime)item[Consts.EventDateField];
                                         List<DateTime> reminderDateList = Utility.CalculateDate(eventDate, reminderTimeCollection);

                                         bool enableReminder = false;
                                         bool.TryParse(item["EnableReminder"].ToString(), out enableReminder);

                                         // ETS_eventDate equals ETS, send email notification
                                         if (reminderDateList.Contains(etsDate)) //(eventDate.Equals(etsDate)) 
                                         {
                                             string sentTo, messageBody;
                                             bool sendStatus = SendEmail(item, out sentTo, out messageBody);

                                             if (enableReminder)
                                                 UpdateSentHistory(item,
                                                     tZone.UTCToLocalTime(
                                                     SPUtility.CreateDateTimeFromISO8601DateTimeString(row[Consts.EstTimetoSendField].ToString())),
                                                     messageBody,
                                                     sentTo,
                                                     sendStatus == true ? "Send OK" : "Send Failed");
#if DEBUG
                                             else
                                                 UpdateSentHistory(item,
                                                     DateTime.Now, "Not Sent", "", "Skipped...");
#endif
                                             if (reminderDateList.Exists(Utility.DateGreaterThanToday))
                                             {
                                                 UpdateEventItem(currentSPWeb.Lists[new Guid(row[Consts.ListIDField].ToString())], item);
                                                 break;  // Exit immediatelly
                                             }
                                             continue; // Grab next items
                                         }
                                         // Possibly a new item
                                         if (UpdateEventItem(currentSPWeb.Lists[new Guid(row[Consts.ListIDField].ToString())], item))
                                         {
                                             break;
                                         }
                                     }
                                 // Delete notify item 
                                 DeleteNotifyItem:
                                     notifySPList.Items.DeleteItemById(notifyListItemID);
                                 }
                                 else
                                 {
                                     // Dirty item, delete it
                                     notifySPList.Items.DeleteItemById(notifyListItemID);
                                 }
                                
                             }
                        }
                    }
                    if (currentSPWeb != null)
                        currentSPWeb.Dispose();

                    if (spWeb != null)
                        spWeb.Dispose();
                    
                }

        }

        private SPListItemCollection GetRecurrence(SPList calendarSPList, string listItemID)
        {
            // Query the recurrence
            SPQuery qry = new SPQuery();
            qry.ExpandRecurrence = true;
            qry.Query = String.Format(@"
                          <Where>
                            <And>
                              <Eq><FieldRef Name='ID' /><Value Type='Int'>{0}</Value></Eq>
                              <DateRangesOverlap>
                                 <FieldRef Name='EventDate' /><FieldRef Name='EndDate' />
                                 <FieldRef Name='RecurrenceID' /><Value Type='DateTime'><Year   /></Value>
                              </DateRangesOverlap>
                            </And>
                          </Where>
                        ", listItemID);

            return calendarSPList.GetItems(qry);
        }

        private DataTable GetSiteDate(SPWeb spWeb)
        {
            SPSiteDataQuery spdQuery = new SPSiteDataQuery();
            spdQuery.Lists = "<Lists ServerTemplate='100' />";
            spdQuery.Webs = "<Webs Scope='Recursive' />";

            // 1. Query _x_Notify_ list, where ETS <= CurrentTime
            spdQuery.Query = String.Format(@"
                                <Where>
                                  <And>
                                      <Leq><FieldRef Name='ETS' />
                                           <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>
                                      </Leq>
                                      <IsNull><FieldRef Name='ATS' /></IsNull>
                                  </And>
                                </Where>
                             ", 
                              SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Now),
                              SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Now.AddMinutes(-6)));


            // Field Name
            spdQuery.ViewFields = @"<FieldRef Name='Title' Nullable='TRUE' />
                                    <FieldRef Name='ETS' Nullable='TRUE' />
                                    <FieldRef Name='ATS' Nullable='TRUE'/>
                                    <FieldRef Name='ListItemID' Nullable='TRUE'/> ";

            return spWeb.GetSiteData(spdQuery);
        }

        private void UpdateSentHistory(SPListItem item, DateTime etsTime, string emailBody, string toAddress, string status)
        {
            using (SPWeb historyWeb = item.Web)
            {
                SPList sentHistoryList;
                try
                {
                    sentHistoryList = historyWeb.Lists[NOTIFICATIONHISTORY];
                }
                catch
                {
                    Guid listGUID = historyWeb.Lists.Add(NOTIFICATIONHISTORY, "Notification History List Name", SPListTemplateType.GenericList);
                    sentHistoryList = historyWeb.Lists[listGUID];
                    sentHistoryList.Fields.Add(Consts.EstTimetoSendField, SPFieldType.DateTime, false);
                    sentHistoryList.Fields.Add(Consts.ActTimetoSendField, SPFieldType.DateTime, false);
                    sentHistoryList.Fields.Add(Consts.ListItemIDField, SPFieldType.Text, false);
                    sentHistoryList.Fields.Add(Consts.StatusField, SPFieldType.Text, false);
                    sentHistoryList.Fields.Add(Consts.EmailBodyField, SPFieldType.Note, false);
                    sentHistoryList.Fields.Add(Consts.ToAddressField, SPFieldType.Text, false);

                }
                if (sentHistoryList == null)
                {
                    return;
                }

                SPListItem sentHistoryListItem = sentHistoryList.Items.Add();
                sentHistoryListItem[Consts.TitleField] = item.ParentList.ID.ToString();
                sentHistoryListItem[Consts.EstTimetoSendField] = etsTime;
                sentHistoryListItem[Consts.ActTimetoSendField] = DateTime.Now;
                sentHistoryListItem[Consts.ListItemIDField] = item.ID.ToString();
                sentHistoryListItem[Consts.StatusField] = status;
                sentHistoryListItem[Consts.EmailBodyField] = emailBody;
                sentHistoryListItem[Consts.ToAddressField] = toAddress;
                sentHistoryListItem.Update();

                SetItemLevelSecurity(item, sentHistoryListItem);
            }
        }

        private void SetItemLevelSecurity(SPListItem itemRoleSource, SPListItem itemTarget)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPWeb eventWeb = new SPSite(itemRoleSource.Web.Url).OpenWeb())
                {
                    SPRoleDefinition roleAdminDef = eventWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);
                    SPListItem item = eventWeb.Lists[itemTarget.ParentList.ID].GetItemById(itemTarget.ID);
                    item.BreakRoleInheritance(false);
                    SPRoleAssignmentCollection roleAssigns = itemRoleSource.RoleAssignments;
                    foreach (SPRoleAssignment roleAssign in itemRoleSource.RoleAssignments)
                        itemTarget.RoleAssignments.Add(roleAssign);
                }

            });
        }       

        private bool UpdateEventItem(SPList notifySPList, SPListItem calendarItem)
        {
            string[] reminderTimeCollection = calendarItem[Consts.ReminderField].ToString().Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries);

            DateTime etsDate = (DateTime) calendarItem[Consts.EventDateField];
            List<DateTime> reminderDateList = Utility.CalculateDate(etsDate, reminderTimeCollection);

            if (reminderDateList.Exists(Utility.DateGreaterThanToday))
            {
                SPListItem eventItem = notifySPList.Items.Add();
                eventItem[Consts.EstTimetoSendField] = reminderDateList.Find(Utility.DateGreaterThanToday);// etsDate;
                eventItem[Consts.TitleField] = calendarItem.ParentList.ID.ToString();
                eventItem[Consts.ListItemIDField] = calendarItem.ID.ToString();
                eventItem.Update();
                return true;
            }
            else
                return false;
        }

        
        protected bool SendEmail(SPListItem item, out string toAddress, out string emailBody)
        {
            DateTime StartTime = (DateTime)item[Consts.EventDateField];
            DateTime EndTime = (DateTime)item["EndDate"];
            string[] author = item[Consts.AuthorField].ToString().Split(new string[] { ";#" }, StringSplitOptions.None);
            StringBuilder authorEmail = new StringBuilder();
            int authorId;
            if(int.TryParse(author[0],out authorId))
            {
                SPUser spUserAuthor = item.Web.SiteUsers.GetByID(authorId);
                authorEmail.Append(spUserAuthor.Email);
            }
            if (item["RemindTo"] != null)
            {
                authorEmail = new StringBuilder();
                authorEmail.Append(item["RemindTo"].ToString());
            }

            StringBuilder MessageBody = new StringBuilder();
            MessageBody.Append("<html><head><title>Schedule Alert Notification</title></head>");
            MessageBody.Append("<body>");
            MessageBody.Append("Dear Sir/Madam,<br/><br/>");
            MessageBody.Append("We would like remind you for the upcoming events: <br>");
            MessageBody.AppendFormat("&nbsp;&nbsp;&nbsp;Event Title : <a href={0}/{1}?ID={2}>{3}</a><br>",
                item.Web.Url,item.ParentList.Forms[PAGETYPE.PAGE_DISPLAYFORM].Url,item.ID,item[Consts.TitleField]);
            MessageBody.AppendFormat("&nbsp;&nbsp;&nbsp;Description : {0}<br>", item["Description"] == null ? " - " : item["Description"].ToString());
            MessageBody.AppendFormat("&nbsp;&nbsp;&nbsp;Location    : {0}<br>", item["Location"] == null ? " - " : item["Location"].ToString());
            MessageBody.AppendFormat("&nbsp;&nbsp;&nbsp;StartTime   : {0}<br>", StartTime.ToString("dddd, MMMM dd, yyyy 'at' HH':'mm"));
            MessageBody.AppendFormat("&nbsp;&nbsp;&nbsp;EndTime     : {0}<br><br>", EndTime.ToString("dddd, MMMM dd, yyyy 'at' HH':'mm"));
            MessageBody.Append("Thank you");
            MessageBody.Append("</body></html>");

            emailBody = MessageBody.ToString();
            toAddress = authorEmail.ToString();

            return SPUtility.SendEmail(item.Web, true, false, authorEmail.ToString(), "Upcoming Schedule Notification", MessageBody.ToString());
        }

        

    }
}
